package cn.edu.suda.ada.database.connection;

import cn.edu.suda.ada.common.config.Config;
import cn.edu.suda.ada.database.connection.pool.ConnectionPool;
import cn.edu.suda.ada.database.connection.pool.DbcpConnectionPool;
import cn.edu.suda.ada.database.connection.pool.DefaultConnectionPool;
import cn.edu.suda.ada.database.exception.DatabaseException;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据库连接类
 * <P>
 * 根据数据库配置文件开辟数据库连接池 <code>ConnectionPool</code> 对象，也就是说一个数据库连接类的实例持有一个数据库连接池。
 * 此外，对于不同地址的配置文件，系统利用单例模式对数据库连接类的实例进行了相应的缓存，以避免数据库连接类的重复初始化。
 * 
 * @author JiangJun
 * 
 */
public class Db {
	/**
	 * 数据库配置信息实例缓存
	 */
	private static Map<String, Config> configs = new HashMap<String, Config>();
	/**
	 * 数据库连接类实例缓存
	 */
	private static Map<Config, ConnectionPool> connectionPools = new HashMap<Config, ConnectionPool>();
	/**
	 * 数据库连接池
	 */
	private ConnectionPool pool = null;
	/**
	 * 数据库连接
	 */
	private Connection connection = null;
	/**
	 * 执行语句
	 */
	private Statement statement = null;
	/**
	 * 当前连接数据库名称
	 */
	private String database;
	/**
	 * 数据表前缀
	 */
	private String prefix;
	
	public String getDatabase() {
		return database;
	}

	public void setDatabase(String database) {
		this.database = database;
	}
	
	public String getPrefix() {
		return prefix;
	}

	public void setPrefix(String prefix) {
		if (null == prefix) prefix = "";
		this.prefix = prefix;
	}

	/**
	 * 实例化
	 * 
	 * @param path 数据库配置文件地址
	 * @throws DatabaseException 无法获取配置文件
	 */
	private Db(String path) throws DatabaseException {
		initConnectionPool(path);
	}
	
	/**
	 * 获得数据库连接池
	 * 
	 * @param path 数据库配置文件地址
	 * @return 数据库连接池对象
	 * @throws DatabaseException 无法获取配置文件
	 */
	@SuppressWarnings("unchecked")
	private synchronized void initConnectionPool(String path) throws DatabaseException {
		Config config = configs.get(path);
		if (config == null) {
			config = DbProperties.getConfig(path);
			configs.put(path, config);
		}
		String database = config.getString("jdbc.database");
		setDatabase(database);
		setPrefix(config.getString("jdbc.prefix"));
		pool = connectionPools.get(config);
		if (pool == null) {
			String poolClassName = config.getString("jdbc.pool.class");
			Class<? extends ConnectionPool> poolClass = null;
			if (poolClassName.isEmpty()) {
				poolClass = DbcpConnectionPool.class;
			} else {
				try {
					Class<?> cls = Class.forName(poolClassName);
					if (ConnectionPool.class.isAssignableFrom(cls)) {
						poolClass = (Class<? extends ConnectionPool>) cls;
					} else {
						throw new DatabaseException("无法实例化数据库连接池类！"); 
					}
				} catch (ClassNotFoundException e) {
					throw new DatabaseException("未找到数据库连接池类！", e);
				}
			}
			pool = DefaultConnectionPool.getConnectionPool(poolClass, config);
			pool.init(config);
			connectionPools.put(config, pool);
		}
	}
	
	/**
	 * 获得一个数据库连接实例
	 * 
	 * @param path 数据库配置文件地址
	 * @return 数据库连接实例
	 * @throws DatabaseException 无法获取配置文件
	 */
	public static Db getInstance(String path) throws DatabaseException {
		return new Db(path);
	}
	
	/**
	 * 获得一个默认数据库连接实例
	 * 
	 * @return 默认数据库连接实例
	 * @throws DatabaseException 无法获取配置文件
	 */
	public static Db getInstance() throws DatabaseException {
		return getInstance(null);
	}
	
	/**
	 * 执行查询操作
	 * <P>
	 * <b>注意使用完结果集之后应关闭数据库连接</b>。
	 * 
	 * @param sql 查询语句
	 * @return 查询结果集
	 * @throws SQLException 数据库操作异常
	 * @throws DatabaseException 无法解析出数据库语句
	 */
	public ResultSet executeQuery(String sql) throws SQLException, DatabaseException {
		if (null == sql) {
			throw new DatabaseException("无法解析出数据库语句！");
		}
		ResultSet result = null;
		connection = pool.getConnection();
		if (null != connection) {
			statement = connection.createStatement();
			// 执行SQL语句
			result = statement.executeQuery(sql);
		}
		return result;
	}

	/**
	 * 执行更新操作
	 * 
	 * @param sql 更新语句
	 * @return 受影响的行数
	 * @throws SQLException 数据库操作异常
	 * @throws DatabaseException 无法获取配置文件或无法解析出数据库语句
	 */
	public int executeUpdate(String sql) throws SQLException, DatabaseException {
		if (null == sql) {
			throw new DatabaseException("无法解析出数据库语句！");
		}
		int rows = -1;
		connection = pool.getConnection();
		if (null != connection) {
			statement = connection.createStatement();
			// 执行SQL语句
			rows = statement.executeUpdate(sql);
			close();
		}
		return rows;
	}
	
	/**
	 * 执行操作
	 * 
	 * @param sql 主要是 DDL 语句
	 * @return 执行结果
	 * @throws SQLException 数据库操作异常
	 * @throws DatabaseException 无法获取配置文件或无法解析出数据库语句
	 */
	public boolean execute(String sql) throws SQLException, DatabaseException {
		if (null == sql) {
			throw new DatabaseException("无法解析出数据库语句！");
		}
		connection = pool.getConnection();
		if (null != connection) {
			statement = connection.createStatement();
			// 执行SQL语句
			statement.execute(sql);
			close();
			
			return true;
		}
		return false;
	}
	
	/**
	 * 开始事务
	 * 
	 * @throws SQLException 数据库操作异常
	 */
	public void startTransaction() throws SQLException {
		connection = pool.getConnection();
		connection.setAutoCommit(false);
		statement = connection.createStatement();
	}
	
	/**
	 * 增加批量操作
	 * 
	 * @param sql 增加的批处理语句
	 * @throws SQLException 数据库操作异常
	 * @throws DatabaseException 无法添加数据库语句
	 */
	public void addBatch(String sql) throws SQLException, DatabaseException {
		if (null == sql) {
			throw new DatabaseException("无法添加数据库语句！");
		}
		statement.addBatch(sql);
	}
	
	/**
	 * 执行批处理操作
	 * 
	 * @return 每条操作影响行数的总和
	 * @throws SQLException 数据库操作异常
	 */
	public int executeBatchUpdate() throws SQLException {
		int[] results = statement.executeBatch();
		int total = 0;
		for (int result : results) {
			total += result;
		}
		return total;
	}
	
	/**
	 * 提交事务
	 * 
	 * @throws SQLException 数据库操作异常
	 * @throws DatabaseException 数据库连接关闭错误
	 */
	public void commit() throws SQLException, DatabaseException {
		connection.commit();
		close();
	}
	
	/**
	 * 关闭数据库连接，返回连接池
	 * 
	 * @throws DatabaseException 数据库连接关闭错误
	 */
	public void close() throws DatabaseException {
		try {
			if (null != statement) statement.close();
			if (null != connection) connection.close();
		} catch (SQLException e) {
			throw new DatabaseException("无法关闭数据库连接！", e);
		}
	}
	
//	@Override
//	public void finalize() throws DatabaseException {
//		try {
//			super.finalize();
//			// 删除连接池
//			((BasicDataSource) dataSource).close();
//		} catch (Throwable e) {
//			throw new DatabaseException("无法关闭数据库连接池！", e);
//		}
//	}
}